Skip to content

发送鼠标点击事件

概述

FBro提供了两种鼠标事件模拟方式:VIP鼠标事件和发送鼠标点击事件。其中发送鼠标点击事件不需要VIP控制接口,可以通过选择器自动获取元素坐标并执行点击操作,特别适用于精确的元素点击场景。

核心概念

两种鼠标事件方式对比

方式优点缺点适用场景
VIP鼠标事件灵活性高,支持复杂操作需要手动获取坐标拖拽、复杂交互
发送鼠标点击事件自动定位元素,精确度高只支持基础点击按钮点击、链接点击

关键组件

  • GetMainTianBiaoFrame():获取浏览器主填表框架
  • GetPoint():通过选择器获取元素坐标
  • SendMouseClickEvent():发送鼠标点击事件
  • FBroSharpMouseEvent:鼠标事件数据结构

基础实现

1. 通过选择器自动点击元素

csharp
/// <summary>
/// 通过CSS选择器点击元素
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="selector">CSS选择器</param>
/// <param name="index">元素索引(从0开始)</param>
/// <returns>是否点击成功</returns>
public bool ClickElementBySelector(IFBroSharpBrowser browser, string selector, int index = 0)
{
    try
    {
        var callback = new FBroSharpJsCallbackSyn();
        
        // 获取元素坐标
        browser.GetMainTianBiaoFrame().GetPoint(selector, index, callback);
        
        if (callback.help != null && callback.help.IsValid)
        {
            // 等待获取坐标结果
            callback.help.WaitEvent(10 * 1000);

            if (callback.help.HavStringData())
            {
                string pointData = callback.help.GetStringData();
                Console.WriteLine($"获取到坐标: {pointData}");
                
                // 解析坐标
                var coordinates = ParseCoordinates(pointData);
                if (coordinates.HasValue)
                {
                    // 执行点击
                    return PerformMouseClick(browser, coordinates.Value.x, coordinates.Value.y);
                }
            }
        }
        
        Console.WriteLine($"无法获取元素坐标: {selector}");
        return false;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"点击元素失败: {ex.Message}");
        return false;
    }
}

/// <summary>
/// 解析坐标字符串
/// </summary>
/// <param name="pointData">坐标数据(格式:x,y)</param>
/// <returns>坐标值</returns>
private (int x, int y)? ParseCoordinates(string pointData)
{
    try
    {
        string[] parts = pointData.Split(',');
        if (parts.Length >= 2)
        {
            float x = float.Parse(parts[0]);
            float y = float.Parse(parts[1]);
            
            return ((int)x, (int)y);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"解析坐标失败: {ex.Message}");
    }
    
    return null;
}

/// <summary>
/// 执行鼠标点击
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="x">X坐标</param>
/// <param name="y">Y坐标</param>
/// <returns>是否成功</returns>
private bool PerformMouseClick(IFBroSharpBrowser browser, int x, int y)
{
    try
    {
        Console.WriteLine($"点击坐标: {x}, {y}");

        // 创建鼠标事件
        var mouseEvent = new FBroSharpMouseEvent
        {
            x = x,
            y = y,
            modifiers = FBroSharpEventFlags.EVENTFLAG_LEFT_MOUSE_BUTTON
        };

        // 发送鼠标按下事件
        browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, false, 1);
        
        // 短暂延迟
        Thread.Sleep(50);
        
        // 发送鼠标释放事件
        browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, true, 1);
        
        Console.WriteLine("鼠标点击事件已发送");
        return true;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"执行鼠标点击失败: {ex.Message}");
        return false;
    }
}

2. 异步版本的元素点击

csharp
/// <summary>
/// 异步点击元素
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="selector">CSS选择器</param>
/// <param name="index">元素索引</param>
/// <returns>是否点击成功</returns>
public async Task<bool> ClickElementAsync(IFBroSharpBrowser browser, string selector, int index = 0)
{
    return await Task.Run(() => ClickElementBySelector(browser, selector, index));
}

方法详细说明

GetPoint 方法

csharp
/// <summary>
/// 取元素坐标
/// </summary>
/// <param name="querySelectorAll">CSS选择器</param>
/// <param name="index">索引:从0开始,选择器的第几个标签</param>
/// <param name="callback">回调函数:用于获取反馈结果的回调</param>
void GetPoint(string querySelectorAll, int index, FBroSharpJsCallback callback);

参数说明:

参数类型说明
querySelectorAllstringCSS选择器,如".class"、"#id"、"button"
indexint元素索引,当选择器匹配多个元素时指定第几个
callbackFBroSharpJsCallback回调对象,用于接收坐标结果

SendMouseClickEvent 方法

csharp
/// <summary>
/// 发送鼠标点击事件
/// </summary>
/// <param name="ButtonType">鼠标按键类型</param>
/// <param name="mouserEvent">鼠标事件数据</param>
/// <param name="mouseUp">是否为释放事件</param>
/// <param name="clickCount">点击次数</param>
void SendMouseClickEvent(FBroSharpMouseButtonType ButtonType, FBroSharpMouseEvent mouserEvent, bool mouseUp, int clickCount);

参数说明:

参数类型说明
ButtonTypeFBroSharpMouseButtonType鼠标按键:MBT_LEFT/MBT_MIDDLE/MBT_RIGHT
mouserEventFBroSharpMouseEvent鼠标事件数据(坐标、修饰键等)
mouseUpboolfalse=按下事件,true=释放事件
clickCountint点击次数,通常为1

枚举类型说明

FBroSharpMouseButtonType

csharp
public enum FBroSharpMouseButtonType
{
    MBT_LEFT,    // 左键
    MBT_MIDDLE,  // 中键(滚轮)
    MBT_RIGHT    // 右键
}

FBroSharpEventFlags

csharp
public enum FBroSharpEventFlags
{
    EVENTFLAG_NONE = 0,                      // 无修饰键
    EVENTFLAG_CAPS_LOCK_ON = 1,             // 大写锁定
    EVENTFLAG_SHIFT_DOWN = 2,               // Shift键按下
    EVENTFLAG_CONTROL_DOWN = 4,             // Ctrl键按下
    EVENTFLAG_ALT_DOWN = 8,                 // Alt键按下
    EVENTFLAG_LEFT_MOUSE_BUTTON = 0x10,     // 左键按下
    EVENTFLAG_MIDDLE_MOUSE_BUTTON = 0x20,   // 中键按下
    EVENTFLAG_RIGHT_MOUSE_BUTTON = 0x40,    // 右键按下
    EVENTFLAG_COMMAND_DOWN = 0x80,          // Command键按下(Mac)
    EVENTFLAG_NUM_LOCK_ON = 0x100,          // 数字锁定
    EVENTFLAG_IS_KEY_PAD = 0x200,           // 来自小键盘
    EVENTFLAG_IS_LEFT = 0x400,              // 左侧键
    EVENTFLAG_IS_RIGHT = 0x800,             // 右侧键
    EVENTFLAG_ALTGR_DOWN = 0x1000,          // AltGr键按下
    EVENTFLAG_IS_REPEAT = 0x2000            // 重复按键
}

FBroSharpMouseEvent 结构

csharp
public struct FBroSharpMouseEvent
{
    public int x;                           // X坐标
    public int y;                           // Y坐标
    public FBroSharpEventFlags modifiers;   // 修饰键标志
}

完整应用示例

1. 智能按钮点击器

csharp
public class SmartButtonClicker
{
    private readonly IFBroSharpBrowser browser;

    public SmartButtonClicker(IFBroSharpBrowser browser)
    {
        this.browser = browser;
    }

    /// <summary>
    /// 尝试多个选择器点击按钮
    /// </summary>
    /// <param name="selectors">按钮选择器列表</param>
    /// <param name="buttonName">按钮名称(用于日志)</param>
    /// <returns>是否成功点击</returns>
    public async Task<bool> TryClickButton(string[] selectors, string buttonName = "按钮")
    {
        foreach (string selector in selectors)
        {
            Console.WriteLine($"尝试点击{buttonName},选择器: {selector}");
            
            if (await ClickElementAsync(browser, selector))
            {
                Console.WriteLine($"{buttonName}点击成功");
                return true;
            }
            
            // 如果当前选择器失败,稍等后尝试下一个
            await Task.Delay(500);
        }
        
        Console.WriteLine($"所有选择器都无法点击{buttonName}");
        return false;
    }

    /// <summary>
    /// 点击登录按钮示例
    /// </summary>
    /// <returns>是否成功</returns>
    public async Task<bool> ClickLoginButton()
    {
        string[] loginSelectors = {
            "#loginBtn",                    // ID选择器
            ".login-button",               // 类选择器
            "button[type='submit']",       // 属性选择器
            "input[value='登录']",         // 值选择器
            ".btn-primary"                 // 主要按钮类
        };
        
        return await TryClickButton(loginSelectors, "登录按钮");
    }

    /// <summary>
    /// 点击上传按钮示例(用于文件上传)
    /// </summary>
    /// <returns>是否成功</returns>
    public async Task<bool> ClickUploadButton()
    {
        string[] uploadSelectors = {
            "input[type='file']",          // 文件输入框
            ".upload-button",              // 上传按钮类
            "#fileUpload",                 // 上传按钮ID
            "button[data-action='upload']", // 数据属性选择器
            ".file-upload-btn"             // 文件上传按钮类
        };
        
        return await TryClickButton(uploadSelectors, "上传按钮");
    }

    private async Task<bool> ClickElementAsync(IFBroSharpBrowser browser, string selector)
    {
        return await Task.Run(() => ClickElementBySelector(browser, selector));
    }

    private bool ClickElementBySelector(IFBroSharpBrowser browser, string selector, int index = 0)
    {
        // 此处使用前面定义的 ClickElementBySelector 方法
        try
        {
            var callback = new FBroSharpJsCallbackSyn();
            browser.GetMainTianBiaoFrame().GetPoint(selector, index, callback);
            
            if (callback.help != null && callback.help.IsValid)
            {
                callback.help.WaitEvent(10 * 1000);
                
                if (callback.help.HavStringData())
                {
                    string pointData = callback.help.GetStringData();
                    var coordinates = ParseCoordinates(pointData);
                    
                    if (coordinates.HasValue)
                    {
                        return PerformMouseClick(browser, coordinates.Value.x, coordinates.Value.y);
                    }
                }
            }
            return false;
        }
        catch
        {
            return false;
        }
    }

    private (int x, int y)? ParseCoordinates(string pointData)
    {
        try
        {
            string[] parts = pointData.Split(',');
            if (parts.Length >= 2)
            {
                float x = float.Parse(parts[0]);
                float y = float.Parse(parts[1]);
                return ((int)x, (int)y);
            }
        }
        catch { }
        return null;
    }

    private bool PerformMouseClick(IFBroSharpBrowser browser, int x, int y)
    {
        try
        {
            var mouseEvent = new FBroSharpMouseEvent
            {
                x = x,
                y = y,
                modifiers = FBroSharpEventFlags.EVENTFLAG_LEFT_MOUSE_BUTTON
            };

            browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, false, 1);
            Thread.Sleep(50);
            browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, true, 1);
            return true;
        }
        catch
        {
            return false;
        }
    }
}

2. 批量操作示例

csharp
public class BatchClickOperations
{
    private readonly IFBroSharpBrowser browser;

    public BatchClickOperations(IFBroSharpBrowser browser)
    {
        this.browser = browser;
    }

    /// <summary>
    /// 批量点击列表项
    /// </summary>
    /// <param name="listSelector">列表选择器</param>
    /// <param name="maxItems">最大点击数量</param>
    /// <param name="interval">点击间隔(毫秒)</param>
    /// <returns>成功点击的数量</returns>
    public async Task<int> ClickListItems(string listSelector, int maxItems = 10, int interval = 1000)
    {
        int successCount = 0;
        
        for (int i = 0; i < maxItems; i++)
        {
            Console.WriteLine($"点击第 {i + 1} 个列表项");
            
            if (ClickElementBySelector(browser, listSelector, i))
            {
                successCount++;
                Console.WriteLine($"第 {i + 1} 个列表项点击成功");
            }
            else
            {
                Console.WriteLine($"第 {i + 1} 个列表项点击失败或不存在");
                break; // 如果某个项不存在,停止循环
            }
            
            // 等待间隔时间
            if (i < maxItems - 1) // 最后一个不需要等待
            {
                await Task.Delay(interval);
            }
        }
        
        Console.WriteLine($"批量点击完成,成功: {successCount}/{maxItems}");
        return successCount;
    }

    /// <summary>
    /// 点击所有匹配的复选框
    /// </summary>
    /// <param name="checkboxSelector">复选框选择器</param>
    /// <returns>成功点击的数量</returns>
    public async Task<int> CheckAllBoxes(string checkboxSelector)
    {
        return await ClickListItems(checkboxSelector, 50, 200); // 最多50个,间隔200ms
    }

    private bool ClickElementBySelector(IFBroSharpBrowser browser, string selector, int index = 0)
    {
        // 复用前面的实现
        try
        {
            var callback = new FBroSharpJsCallbackSyn();
            browser.GetMainTianBiaoFrame().GetPoint(selector, index, callback);
            
            if (callback.help != null && callback.help.IsValid)
            {
                callback.help.WaitEvent(5 * 1000); // 减少等待时间提高效率
                
                if (callback.help.HavStringData())
                {
                    string pointData = callback.help.GetStringData();
                    string[] parts = pointData.Split(',');
                    
                    if (parts.Length >= 2)
                    {
                        int x = (int)float.Parse(parts[0]);
                        int y = (int)float.Parse(parts[1]);
                        
                        var mouseEvent = new FBroSharpMouseEvent
                        {
                            x = x,
                            y = y,
                            modifiers = FBroSharpEventFlags.EVENTFLAG_LEFT_MOUSE_BUTTON
                        };

                        browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, false, 1);
                        Thread.Sleep(30);
                        browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, true, 1);
                        return true;
                    }
                }
            }
            return false;
        }
        catch
        {
            return false;
        }
    }
}

与VIP鼠标事件的对比

使用场景选择

场景推荐方式原因
点击已知选择器的元素发送鼠标点击事件自动定位,精确度高
点击固定坐标位置VIP鼠标事件直接指定坐标,速度快
拖拽操作VIP鼠标事件支持连续移动操作
复杂鼠标交互VIP鼠标事件功能更完整
文件上传按钮点击发送鼠标点击事件绕过JavaScript限制

性能对比

方面发送鼠标点击事件VIP鼠标事件
执行速度较慢(需要获取坐标)快(直接操作)
准确性高(自动定位)中(依赖坐标准确性)
易用性高(选择器定位)中(需要手动获取坐标)
功能完整性低(仅基础点击)高(支持所有鼠标操作)

最佳实践

1. 错误处理和重试

csharp
public async Task<bool> ClickWithRetry(IFBroSharpBrowser browser, string selector, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        Console.WriteLine($"第 {attempt} 次尝试点击: {selector}");
        
        if (ClickElementBySelector(browser, selector))
        {
            Console.WriteLine("点击成功");
            return true;
        }
        
        if (attempt < maxRetries)
        {
            Console.WriteLine($"第 {attempt} 次尝试失败,等待重试...");
            await Task.Delay(1000 * attempt); // 递增延迟
        }
    }
    
    Console.WriteLine($"所有尝试都失败了: {selector}");
    return false;
}

2. 选择器优先级策略

csharp
public string[] GetPrioritySelectors(string elementType)
{
    return elementType.ToLower() switch
    {
        "upload" => new[] {
            "input[type='file']",
            ".upload-btn",
            "#uploadButton",
            "button[data-action='upload']"
        },
        "submit" => new[] {
            "button[type='submit']",
            "input[type='submit']",
            ".submit-btn",
            "#submitButton"
        },
        "login" => new[] {
            "#loginBtn",
            ".login-button",
            "button[data-action='login']",
            ".btn-login"
        },
        _ => new[] { $"#{elementType}", $".{elementType}", elementType }
    };
}

3. 日志和调试

csharp
public class ClickLogger
{
    public static void LogClick(string selector, int x, int y, bool success)
    {
        string status = success ? "成功" : "失败";
        Console.WriteLine($"[鼠标点击] {selector} -> ({x}, {y}) - {status}");
    }

    public static void LogCoordinates(string selector, string coordinates)
    {
        Console.WriteLine($"[坐标获取] {selector} -> {coordinates}");
    }
}

注意事项

1. 坐标系统

  • 坐标相对于浏览器视口的左上角
  • 需要考虑页面滚动位置的影响
  • 不同显示器DPI可能影响坐标精度

2. 时机控制

  • 确保页面元素已完全加载
  • 添加适当的延迟避免操作过快
  • 使用回调机制等待异步操作完成

3. 兼容性

  • 不同网站的元素结构可能不同
  • 准备多个候选选择器提高成功率
  • 定期验证选择器的有效性

通过这些方法和最佳实践,您可以实现稳定可靠的鼠标点击自动化操作。

如果文档对您有帮助,欢迎 请喝咖啡 ☕ | 软件发布 | 源码购买